package com.yvon.boot.mybatis.search;


import com.baomidou.mybatisplus.core.conditions.interfaces.Compare;
import com.baomidou.mybatisplus.core.conditions.interfaces.Func;
import com.baomidou.mybatisplus.core.conditions.interfaces.Join;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.google.common.base.CaseFormat;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.yvon.boot.mybatis.page.SearchCriteria;
import com.yvon.boot.mybatis.search.SearchParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.nio.channels.IllegalSelectorException;
import java.security.InvalidParameterException;
import java.util.*;
import java.util.stream.Collectors;

@Data
@AllArgsConstructor
@Slf4j
public class BaseQuery {

    public <T> QueryWrapper<T> buildQuery(QueryWrapper<T> query) {
        parseQuery(query);
        return query;
    }

    public <T> QueryChainWrapper<T> buildQuery(QueryChainWrapper<T> query) {
        parseQuery(query);
        return query;
    }

    private void parseQuery(QueryWrapper query) {
        Class<?> clazz = this.getClass();
        List<SearchParam> between = new ArrayList<>();
        if (BaseQuery.class != clazz && PageQuery.class != clazz) {
            for (Field field : clazz.getDeclaredFields()) {
                String filedNameLower = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
                Object filedValue = null;
                try {
                    field.setAccessible(true);
                    filedValue = field.get(this);
                } catch (IllegalSelectorException | IllegalAccessException e) {
                    log.warn("get filed value failed: {}", e.getMessage(), e);
                }
                SearchParam searchParam = new SearchParam();
                // 解析前端传入的排序规则
                if (field.isAnnotationPresent(MpOrder.class)) {
                    MpOrder mpOrder = field.getAnnotation(MpOrder.class);
                    MpOrder.Order order = mpOrder.order();
                    if (Objects.nonNull(filedValue)) {
                        order = (MpOrder.Order) filedValue;
                    }
                    String filedName = getFiledName(filedNameLower, mpOrder.alias(), mpOrder.fieldName());
                    if (MpOrder.Order.asc.equals(order)) {
                        query.orderByAsc(filedName);
                    }
                    if (MpOrder.Order.desc.equals(order)) {
                        query.orderByAsc(filedName);
                    }
                    continue;
                }
                // 解析搜索条件
                if (field.isAnnotationPresent(MpSearch.class)) {
                    MpSearch mpSearch = field.getAnnotation(MpSearch.class);
                    if (mpSearch.ignore()) {
                        continue;
                    }
                    String filedName = getFiledName(filedNameLower, mpSearch.alias(), mpSearch.fieldName());
                    searchParam.setFieldName(filedName);
                    searchParam.setOperate(mpSearch.operate());
                    searchParam.setValue(filedValue);
                    // 默认字段排序
                    if (MpOrder.Order.asc.equals(mpSearch.autoOrder())) {
                        query.orderByAsc(filedName);
                    }
                    if (MpOrder.Order.desc.equals(mpSearch.autoOrder())) {
                        query.orderByAsc(filedName);
                    }
                    if (MpSearch.Operator.betweenEnd.equals(searchParam.getOperate()) || MpSearch.Operator.betweenStart.equals(searchParam.getOperate())) {
                        if (Objects.nonNull(searchParam.getValue()) && Objects.nonNull(searchParam.getValue1())) {
                            between.add(searchParam);
                        }
                        continue;
                    }
                } else if (Objects.nonNull(filedValue)){
                    //无注解时，默认eq
                    searchParam.setFieldName(filedNameLower);
                    searchParam.setOperate(MpSearch.Operator.eq);
                    searchParam.setValue(filedValue);
                }
                if (filterQuery(searchParam)) {
                    continue;
                }
                this.queryFilterChain(query, searchParam);
            }
            buildBetweenParam(query, between);
        }
    }

    private void parseQuery(QueryChainWrapper query) {
        Class<?> clazz = this.getClass();
        List<SearchParam> between = new ArrayList<>();
        if (BaseQuery.class != clazz && PageQuery.class != clazz) {
            for (Field field : clazz.getDeclaredFields()) {
                String filedNameLower = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
                Object filedValue = null;
                try {

                    field.setAccessible(true);
                    filedValue = field.get(this);
                } catch (IllegalSelectorException | IllegalAccessException e) {
                    log.warn("get filed value failed: {}", e.getMessage(), e);
                }
                SearchParam searchParam = new SearchParam();
                // 解析前端传入的排序规则
                if (field.isAnnotationPresent(MpOrder.class)) {
                    MpOrder mpOrder = field.getAnnotation(MpOrder.class);
                    MpOrder.Order order = mpOrder.order();
                    if (Objects.nonNull(filedValue)) {
                        order = (MpOrder.Order) filedValue;
                    }
                    String filedName = getFiledName(filedNameLower, mpOrder.alias(), mpOrder.fieldName());
                    if (MpOrder.Order.asc.equals(order)) {
                        query.orderByAsc(filedName);
                    }
                    if (MpOrder.Order.desc.equals(order)) {
                        query.orderByAsc(filedName);
                    }
                    continue;
                }
                // 解析搜索条件
                if (field.isAnnotationPresent(MpSearch.class)) {
                    MpSearch mpSearch = field.getAnnotation(MpSearch.class);
                    if (mpSearch.ignore()) {
                        continue;
                    }
                    String filedName = getFiledName(filedNameLower, mpSearch.alias(), mpSearch.fieldName());
                    searchParam.setFieldName(filedName);
                    searchParam.setOperate(mpSearch.operate());
                    searchParam.setValue(filedValue);
                    // 默认字段排序
                    if (MpOrder.Order.asc.equals(mpSearch.autoOrder())) {
                        query.orderByAsc(filedName);
                    }
                    if (MpOrder.Order.desc.equals(mpSearch.autoOrder())) {
                        query.orderByAsc(filedName);
                    }
                    if (MpSearch.Operator.betweenEnd.equals(searchParam.getOperate()) || MpSearch.Operator.betweenStart.equals(searchParam.getOperate())) {
                        if (Objects.nonNull(searchParam.getValue()) || Objects.nonNull(searchParam.getValue1())) {
                            between.add(searchParam);
                        }
                        continue;
                    }
                } else if (Objects.nonNull(filedValue)){
                    //无注解时，默认eq
                    searchParam.setFieldName(filedNameLower);
                    searchParam.setOperate(MpSearch.Operator.eq);
                    searchParam.setValue(filedValue);
                }
                if (filterQuery(searchParam)) {
                    continue;
                }
                this.queryFilterChain(query, searchParam);
            }
            buildBetweenParam(query, between);
        }
    }


    private void queryFilterChain(QueryChainWrapper query, SearchParam searchParam) {
        //searchParam.setFieldName(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, searchParam.getFieldName()));
        this.doQueryFilterChain(this.doCompareFilter(query, searchParam), this.doFuncFilter(query, searchParam), this.doJoinFilter(query, searchParam), Boolean.FALSE);
    }

    private void queryFilterChain(QueryWrapper query, SearchParam searchParam) {
//        searchParam.setFieldName(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, searchParam.getFieldName()));
        this.doQueryFilterChain(this.doCompareFilter(query, searchParam), this.doFuncFilter(query, searchParam), this.doJoinFilter(query, searchParam), Boolean.FALSE);
    }

    private void doQueryFilterChain(Boolean compare, Boolean func, Boolean join, Boolean nested) {
        if (!compare) {
            if (!func) {
                if (!join) {
                    if (!nested) {
                        ;
                    }
                }
            }
        }
    }

    private <T> Boolean doJoinFilter(Join<T> query, SearchParam searchParam) {
        String endDate;
        switch(searchParam.getOperate()) {
            case betweenStart:
            case betweenEnd:
                endDate = (String) searchParam.getValue();
                query.apply("date_format(" + searchParam.getFieldName() + ",'" + getMySqlDateFormatByTime(endDate) + "') >= {0}", new Object[]{endDate});

                endDate = (String) searchParam.getValue1();
                query.apply("date_format(" + searchParam.getFieldName() + ",'" + getMySqlDateFormatByTime(endDate) + "') <= {0}", new Object[]{endDate});

                break;
            case notBetweenStart:
            case notBetweenEnd:
                endDate = (String) searchParam.getValue();
                query.apply("date_format(" + searchParam.getFieldName() + ",'" + getMySqlDateFormatByTime(endDate) + "') <= {0}", new Object[]{endDate});

                endDate = (String) searchParam.getValue1();
                query.apply("date_format(" + searchParam.getFieldName() + ",'" + getMySqlDateFormatByTime(endDate) + "') >= {0}", new Object[]{endDate});

                break;
            case orLike:
                endDate = (String) searchParam.getValue();
                query.apply("concat(" + searchParam.getFieldName() +") like concat('%', {0}, '%')", new Object[]{endDate});
                break;
            default:
                return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }

    private <T> Boolean doFuncFilter(Func<T, String> query, SearchParam searchParam) {
        switch(searchParam.getOperate()) {
            case in:
                query.in(searchParam.getFieldName(), searchParam.getValue());
                break;
            case notIn:
                query.notIn(searchParam.getFieldName(), searchParam.getValue());
                break;
            case isNull:
                query.isNotNull(searchParam.getFieldName());
                query.isNull(searchParam.getFieldName());
                break;
            case isNotNull:

                break;
            default:
                return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }

    private <T> Boolean doCompareFilter(Compare<T, String> query, SearchParam searchParam) {
        switch(searchParam.getOperate()) {
            case eq:
                query.eq(searchParam.getFieldName(), searchParam.getValue());
                break;
            case ne:
                query.ne(searchParam.getFieldName(), searchParam.getValue());
                break;
            case gt:
                query.gt(searchParam.getFieldName(), searchParam.getValue());
                break;
            case ge:
                query.ge(searchParam.getFieldName(), searchParam.getValue());
                break;
            case lt:
                query.lt(searchParam.getFieldName(), searchParam.getValue());
                break;
            case le:
                query.le(searchParam.getFieldName(), searchParam.getValue());
                break;
            case like:
                query.like(searchParam.getFieldName(), searchParam.getValue());
                break;
            case notLike:
                query.notLike(searchParam.getFieldName(), searchParam.getValue());
                break;
            case likeLeft:
                query.likeLeft(searchParam.getFieldName(), searchParam.getValue());
                break;
            case likeRight:
                query.likeRight(searchParam.getFieldName(), searchParam.getValue());
                break;
            default:
                return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }

    private List<String> stringToList(@NonNull String toSplit, @Nullable String delimiter) {
        if (Strings.isNullOrEmpty(toSplit)) {
            throw new IllegalArgumentException("toSplit can not be null or empty!");
        } else {
            if (Strings.isNullOrEmpty(delimiter)) {
                delimiter = ",";
            }

            ArrayList searchValue;
            if (!toSplit.contains(delimiter)) {
                searchValue = Lists.newArrayList(new String[]{toSplit});
            } else {
                searchValue = Lists.newArrayList(Lists.newArrayList(toSplit.split(delimiter)));
            }

            return searchValue;
        }
    }

    /**
     * 提起名字
     *
     * @param filed            提起
     * @param searchAlias      搜索别名
     * @param searchFiledNames 搜索提起名字
     * @return {@code String }
     */
    public String getFiledName(String filed, String searchAlias, String[] searchFiledNames) {
        if (!Strings.isNullOrEmpty(searchAlias)) {
            if (Objects.nonNull(searchFiledNames) && searchFiledNames.length > 0) {
                return Arrays.stream(searchFiledNames).map(filed1 -> searchAlias.concat(".").concat(filed1)).collect(Collectors.joining(","));
            }
            return searchAlias.concat(".").concat(filed);
        }
        if (Objects.nonNull(searchFiledNames) && searchFiledNames.length > 0) {
            return String.join(",", searchFiledNames);
        }
        return filed;
    }

    public void saveBetweenParam(SearchParam searchParam, List<SearchParam> between) {
        if (MpSearch.Operator.betweenEnd.equals(searchParam.getOperate()) || MpSearch.Operator.betweenStart.equals(searchParam.getOperate())) {
            if (Objects.nonNull(searchParam.getValue()) && Objects.nonNull(searchParam.getValue1())) {
                between.add(searchParam);
            }

//            between.add(searchParam);
//            between.forEach(param -> {
//                if (param.getFieldName().equals(searchParam.getFieldName()) && !param.getOperate().equals(searchParam.getOperate())) {
//                    searchParam.setValue(MpSearch.Operator.betweenStart.equals(searchParam.getOperate()) ? searchParam.getValue() : param.getValue());
//                    searchParam.setValue1(MpSearch.Operator.betweenEnd.equals(searchParam.getOperate()) ? param.getValue() : searchParam.getValue());
//
//                }
//            });

        }
    }

    public void buildBetweenParam(QueryChainWrapper queryWrapper, List<SearchParam> between) {
        between.forEach(searchParam -> {
            if (MpSearch.Operator.betweenStart.equals(searchParam.getOperate())) {
                List<SearchParam> collect = between.stream().filter(param -> param.getFieldName().equals(searchParam.getFieldName()) && MpSearch.Operator.betweenEnd.equals(param.getOperate())).collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(collect)) {
                    searchParam.setValue1(collect.get(0).getValue());
                    this.queryFilterChain(queryWrapper, searchParam);
                }
            }
        });
    }

    public void buildBetweenParam(QueryWrapper queryWrapper, List<SearchParam> between) {
        between.forEach(searchParam -> {
            if (MpSearch.Operator.betweenStart.equals(searchParam.getOperate())) {
                List<SearchParam> collect = between.stream().filter(param -> param.getFieldName().equals(searchParam.getFieldName()) && MpSearch.Operator.betweenEnd.equals(param.getOperate()) && Objects.nonNull(param.getValue())).collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(collect)) {
                    searchParam.setValue1(collect.get(0).getValue());
                    this.queryFilterChain(queryWrapper, searchParam);
                }
            }
        });
    }

    public boolean filterQuery(SearchParam searchParam) {

        return Objects.isNull(searchParam.getValue()) && (Objects.isNull(searchParam.getOperate()) ||
                !MpSearch.Operator.isNull.equals(searchParam.getOperate()) ||
                !searchParam.getOperate().equals(MpSearch.Operator.isNotNull));
    }

    public String getMySqlDateFormatByTime(String time) {
        if (time.matches("^\\d{4}-\\d{1,2}$")) {
            return "%Y-%m";
        } else if (time.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
            return "%Y-%m-%d";
        } else if (time.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
            return "%Y-%m-%d %H:%i";
        } else if (time.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
            return "%Y-%m-%d %H:%i:%S";
        } else {
            throw new InvalidParameterException("时间格式不正确：" + time);
        }
    }

}
